﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

using Noris.Tools.TraceVisualiser.Interface;
using Noris.Tools.TraceVisualiser.Support;
using Noris.Tools.TraceVisualiser.Components;
using Noris.Tools.TraceVisualiser.TraceData;
using Noris.Tools.TraceVisualiser.Green;

namespace Noris.Tools.TraceVisualiser.Green.AplThreads
{
    #region TraceReader : reader pro soubory typu Klientský trace Noris
    /// <summary>
    /// TraceReader : reader pro soubory typu Klientský trace Noris
    /// </summary>
    public class TraceReader : ITraceReader
    {
		#region IPlugin Members
		/// <summary>
		/// Příznak, že Plugin je aktivní ve standardním běhu (tj. při běžném užiavtelském spuštění).
		/// Plně funkční pluginy tedy vrací true.
		/// Pluginy, které jsou ve vývoji, vrací false, takové jsou zobrazeny / spouštěny pouze v aplikaci, která běží v režimu Debug.
		/// </summary>
		bool IPlugin.Active { get { return true; } }
		#endregion
		#region ITraceReader, IDisposable Members
        /// <summary>
        /// Zde reader vrací výraz, který se použije v dialogu při výběru vstupních trace soubor.
        /// Různé readery mají různé vstupní soubory s různou příponou, a my nechceme žádný reader opomenout.
        /// Reader může umět zpracovávat více různých koncovek, proto se používá string[], každá položka obsahuje jeden filtr.
        /// Každý jednotlivý filtr je ve tvaru: "Textový popisek|filtr", například: "Aplikační trace Green (*.csv)|*csv".
        /// </summary>
        string[] ITraceReader.FileOpenFilters
        {
            get
            {
                return new string[]
                {
                    "Helios Green thread files (*.csv)|*.csv"
                };
            }
        }
        /// <summary>
        /// Metoda má za úkol podívat se na název souboru,
        /// a vyjádřit se k tomu, zda se bude chtít podívat i dovnitř souboru (tím, že nastaví args.CheckContent = true).
        /// Metoda by měla (pokud se tento TraceReader dokáže s názvem souboru vypořádat) rozebrat název souboru na jméno serveru, a oddělit i pořadové číslo z názvu.
        /// Metoda absolutně nemá za úkol otevírat soubor, pracuje jen s názvem.
        /// Pokud se metoda bude chtít podívat do souboru, nastaví args.CheckContent = true, a za chvíli se vyvolá další metoda: this.CheckContent().
        /// </summary>
        /// <param name="args">Data o souboru (název a výsledky této metody)</param>
        /// <returns></returns>
        void ITraceReader.CheckFileName(TraceReaderCheckNameArgs args)
        {
            // Nechci se dívat dovnitř (možná časem změním názor):
            args.CheckContent = false;

            // Přípona má být ".csv"
            System.IO.FileInfo fileInfo = args.FileInfo;
            if (!String.Equals(fileInfo.Extension, ".csv", StringComparison.OrdinalIgnoreCase))
                return;

            // Jméno má začínat "Trace" a končit _threads:
            string name = System.IO.Path.GetFileNameWithoutExtension(args.FileInfo.Name);
            if (!name.StartsWith("trace", StringComparison.OrdinalIgnoreCase))
                return;
			if (!name.EndsWith("_threads", StringComparison.OrdinalIgnoreCase))
				return;

            // ..., a má mít určitou strukturu:
            string fileRoot, nameDate;
            if (!ParseFileName(args.FileInfo.Name, out fileRoot, out nameDate))
                return;

            DateTime date = DetectNameDate(nameDate);
            if (date == DateTime.MinValue)
                return;

            // Vypadá to nadějně, určíme název serveru (adresář, základ, číslo dne):
            args.ServerName = NorisThreadConstants.ServerName + "_" + System.IO.Path.Combine(fileInfo.Directory.FullName, fileRoot) + "_" + date.ToString("yyyyMMdd");

            // Do argumentu vložím datum souboru odvozené z jeho záhlaví:
            args.TraceDate = date;

            // Šlo by to, podíváme se dovnitř:
            args.CheckContent = true;
        }
        /// <summary>
        /// Metoda je volaná tehdy, když tento reader se přihlásil ke čtení záhlaví souboru (v metodě CheckFileName, nastavil args.CheckContent = true).
        /// Tato metoda nemá za úkol číst data, ale podívat se dovnitř, a usoudit, zda my chceme tento soubor zpracovávat.
        /// Soubor je otevřen, a je k němu přístup přes argument
        /// </summary>
        /// <param name="args"></param>
        void ITraceReader.CheckContent(TraceReaderCheckContentArgs args)
        {
            // Nechci soubor zpracovávat (možná časem změním názor):
            args.Priority = 0M;

            // Ověřím titulkový řádek:
            // Zatím umím jen tento řádek:
			string expected = "Time;ThreadId;ManagedId;User;Profile;Created;Last resume;Last suspend;Suspended;Overall time;Suspend Count; Type;Action;Description";
			string title = args.ReadFirstLine();
			if (title.Length != expected.Length) return;
            if (String.Compare(title, expected, StringComparison.CurrentCultureIgnoreCase) != 0) return;

            // Najdu začátek a konec dat v souboru:
            TimeRange contentTime = TimeRange.Empty;
            string line = args.FileStreamReader.ReadLine();
            contentTime.Begin = _GetDateFromColumn(args.TraceDate, line, 5);

            // Najdu poslední řádek, který obsahuje alespoň 13 prvků oddělených středníkem:
            line = args.ReadLastLine(";", 13);
            contentTime.End = _GetDateFromColumn(contentTime.Begin, line, 0);

            // Pokud jsem našel nějaká data, pak bych takový soubor mohl zpracovat:
            if (contentTime.Begin > DateTime.MinValue)
            {
                args.Priority = 1M;
                args.ContentTime = contentTime;
				args.TraceType = NorisThreadConstants.TraceType;
                args.ServerTimePriority = 2;                             // Tento server má druhý nejdůvěryhodnější čas
                this._PrepareContentElementTexts(args);
                this._PrepareContentTreePaths(args);
            }
        }
        /// <summary>
        /// Vrátí datum a čas složené z dodaného data a z času přečteného z prvního [0] prvku řádku (line) před oddělovačem ";"
        /// </summary>
        /// <param name="dateTime"></param>
        /// <param name="line"></param>
        /// <returns></returns>
        private DateTime _GetDateFromColumn(DateTime dateTime, string line, int itemIndex)
        {
            string[] items = line.Split(';');
            if (items.Length < itemIndex) return dateTime;
            return MainInfo.ConvertDateTime(dateTime, items[itemIndex]);
        }
        /// <summary>
        /// Sestaví texty, které se budou vypisovat v elementu a v tooltipu
        /// </summary>
        /// <param name="args"></param>
        private void _PrepareContentElementTexts(TraceReaderCheckContentArgs args)
        {
            string pathDir1 = args.FileInfo.Directory.Name;         // Jméno prvního up adresáře
            string fileDir1 = Path.Combine(pathDir1, args.FileInfo.Name);  // jméno s prvním adresářem a s příponou
            string fileName = args.FileInfo.FullName;
            string fileRoot, nameDate;

            // Texty do elementu:
            if (ParseFileName(fileName, out fileRoot, out nameDate))
                args.ElementTexts = new string[]
                {
                    nameDate,                                       // datum
                    Path.GetFileNameWithoutExtension(fileName),     // jméno bez adresáře a bez přípony
                    args.FileInfo.Name,                             // jméno bez adresáře s příponou
                    fileDir1,                                       // jméno s prvním adresářem a s příponou
                    fileName                                        // full filename
                };
            else
                args.ElementTexts = new string[] 
                {
                    Path.GetFileNameWithoutExtension(fileName),     // jméno bez adresáře a bez přípony
                    args.FileInfo.Name,                             // jméno bez adresáře s příponou
                    fileDir1,                                       // jméno s prvním adresářem a s příponou
                    fileName                                        // full filename
                };

            // Tooltip:
            args.ElementTooltip = "Vstupní soubor: " + fileDir1 + Environment.NewLine +
                "Obsahuje data za čas: " + args.ContentTime.StringShort;
        }
        /// <summary>
        /// Do argumentu připraví nody, které budeme později pravděpodobně používat:
        /// </summary>
        /// <param name="args"></param>
        private void _PrepareContentTreePaths(TraceReaderCheckContentArgs args)
        {
            // Plná cesta k nodu, absolutní (true), node = Klienti, graf = elementy se nenačítají, podřízené řádky se načítají dynamicky, třídí se podle textu:
            TreePath nodeClient = new TreePath(true);
            nodeClient.AddItem(CreateTreePathItemThread());

            // Připravené dva nody dáme do TreePaths:
            args.TreePaths = new TreePath[] { nodeClient };
        }
        /// <summary>
        /// Metoda je volaná pro zpracování celého obsahu dat.
        /// Veškerou podporu má v argumentu TraceReaderReadItemArgs args.
        /// Metoda si může libovolně zpracovat vstupující soubor 
        /// a vytvářet z něj data ve formě jednotlivých záznamů typu ITraceItem.
        /// Tyto záznamy předává ke zpracování do datového systému pomocí volání metody argumentu ProcessItem(ITraceItem item).
        /// Metoda takto může vynechat některé věty (nedávat je ke zpracování), anebo některé věty zpracovat více způsoby
        /// (například z jedné věty trace vytvoří dvě položky: jednu ktertá představuje aktivitu uživatele, a druhou která popisuje stav paměti).
        /// Tím, že zpracování probíhá v jedné metodě, si reader může udržovat kontinuální povědomí o načítaných datech, 
        /// a například o stavu paměti posílat položky při změně hodnoty (není třeba posílat 1000 x jednu hodnotu stavu paměti).
        /// </summary>
        /// <param name="args"></param>
        void ITraceReader.ProcessContent(TraceReaderProcessContentArgs args)
        {
            this._ProcessContent(args);
        }
        /// <summary>
        /// Tato metoda má za úkol vytvořit a vrátit logickou událost na základě fyzických dat načtených z trace
        /// (před malinkou chvilkou je objekt osobně načetl a vrátil v metodě ReadItem()).
        /// Pokud se vrátí null, pak tato položka trace nemá být zakládána do evidence.
        /// </summary>
        IEventItem ITraceReader.CreateIEventItem(IEventEdge data)
        {
            return _CreateIEventItem(data);
        }
        /// <summary>
        /// Metoda, která má za úkol vytvořit celou plně definovanou cestu k nodu.
        /// Tato metoda se volá zcela výjimečně - tehdy, když tento objekt předepisuje nějakou TargetPath, která dosud neexistuje.
        /// Pak si jádro vyžádá plnou cestu k nodu, včetně textů a definice všech nodů.
        /// </summary>
        /// <param name="path"></param>
        /// <returns></returns>
        void ITraceReader.CreateFullTreePath(TraceReaderCreatePathArgs args)
        {
            this.CreateFullTreePath(args);
        }
        void IDisposable.Dispose()
        {
        }
        #endregion
        #region Rozklad názvu souboru na prvky
        /// <summary>
        /// Ze jména souboru (adresář není třeba zadávat, ale nevadí) najde základ a datovou část (zde není číslo souboru).
		/// Soubor se jmenuje typicky : "d:\Data\TestThreads\Trace_Noris_ASB2_111107_threads.csv"
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="fileRoot"></param>
        /// <param name="nameDate"></param>
        internal static bool ParseFileName(string fileName, out string fileRoot, out string nameDate)
        {
            fileRoot = null;
            nameDate = null;
            string name = System.IO.Path.GetFileNameWithoutExtension(fileName);
            int x;

            // Na konci je text _threads, ten pouze odeberu:
			x = name.ToLower().LastIndexOf("_threads");
            if (x <= 1 || x >= name.Length) return false;
            name = name.Substring(0, x);

			// Před textem "_threads" je datum:
            x = name.LastIndexOf("_");
            if (x <= 1 || x >= name.Length) return false;
            nameDate = name.Substring(x + 1);
            fileRoot = name.Substring(0, x);

            return true;
        }
        /// <summary>
        /// Z textu s datem v názvu souboru (text ve formě "YYMMDD") určí a vrátí datum trace.
        /// Pokud nejde určit, vrací MinValue.
        /// </summary>
        /// <param name="nameDate"></param>
        /// <returns></returns>
        internal static DateTime DetectNameDate(string nameDate)
        {
            DateTime result = DateTime.MinValue;
            if (!String.IsNullOrEmpty(nameDate) && nameDate.Length == 6)
            {
                int year, month, day;
                if (Int32.TryParse(nameDate.Substring(0, 2), out year) &&
                    Int32.TryParse(nameDate.Substring(2, 2), out month) &&
                    Int32.TryParse(nameDate.Substring(4, 2), out day))
                {
                    if (month > 0 && month <= 12 &&
                        day > 0 && day <= 31)
                    {
                        try
                        {
                            result = new DateTime(2000 + year, month, day);
                        }
                        catch
                        {
                            result = DateTime.MinValue;
                        }
                    }
                }
            }
            return result;
        }
        #endregion
		#region Fyzické načítání obsahu souboru a zpracování na položky ITraceItem
		/// <summary>
		/// Zpracuje data do jednotlivých položek
		/// </summary>
		/// <param name="args"></param>
		private void _ProcessContent(TraceReaderProcessContentArgs args)
		{
			TraceServerPersistData persistData = TraceServerPersistData.GetFrom(args.Server.Tag);
			try
			{
				this.StringHeap = args.Main.StringHeap;
				this.TitleLine = args.FileStreamReader.ReadLine();
				this.LineNumber = 0;

                // Předáme datum procesu z trace souboru do položek (tam se uvádí jen Time, ne Date):
                persistData.TraceFileDate = args.TraceDate;

				while (!args.FileStreamReader.EndOfStream)
				{
					string lineText = args.FileStreamReader.ReadLine();
					this.LineNumber++;

					// Načtu řádek do objektu, pro snadné zpracovávání:
					TraceItem traceItem = TraceItem.CreateItem(args.Main.StringHeap, (this.LineNumber + 1), lineText, ref persistData);
                    if (traceItem != null)
                    {
                        // Najdu/vytvořím proces (uživatele) a zkusím v něm najít událost (thread):
                        string processKey = traceItem.ProcessKey;       // Login.Profile = klíč procesu = řádek v grafu
                        ProcessInfo processInfo = args.Server.FindOrCreateProcess(processKey);  // Data procesu (obsahuje thready tohoto uživatele)
                        string eventKey = traceItem.EventKey;           // ThreadId.CreatedSec = klíč události
                        EventInfo eventInfo = processInfo.TryFindEvent(eventKey);

                        // Pokud event neexistuje, založím nový a předám do metody k přidání do datové základny:
                        if (eventInfo == null)
                        {
                            EventItem eventItem = EventItem.CreateEventItem(args.Main.StringHeap, traceItem);
                            eventItem.AddData(traceItem, args.Trace);
                            args.ProcessEvent(eventItem);
                        }
                        else
                        {   // V daném procesu (uživatel) už existuje event (thread + čas), přidám do něj zdejší data:
                            EventItem eventItem = eventInfo.EventItem as EventItem;
                            if (eventItem != null)
                                eventItem.AddData(traceItem, args.Trace);
                        }
                    }
				}
			}
			finally
			{
				args.Server.Tag = persistData;
				this.StringHeap = args.Main.StringHeap;
			}
		}
		/// <summary>
		/// Reference na zásobník textů
		/// </summary>
		protected StringHeap StringHeap { get; set; }
		protected string TitleLine { get; set; }
		protected int LineNumber { get; set; }
		#endregion
		#region Podpora pro tvorbu nodů grafu (řádky: klíče, názvy, definice grafiky a chování řádku)
		/// <summary>
		/// Sestavení plné cesty k nodu na základě jejího stringu
		/// </summary>
		/// <param name="args"></param>
		protected void CreateFullTreePath(TraceReaderCreatePathArgs args)
		{
			// Připravím si polotovar (tam se mi rozeberou jednotlivé složky cesty):
			TreePath input = new TreePath(args.Path);
			List<TreePathItem> items = input.Items;

			// Připravím si výsledek:
			TreePath result = new TreePath(input.AbsolutePath);
			string status = (input.AbsolutePath ? "root" : "node");           // Jakou část cesty budu nyní zpracovávat? Dynamicky se posouvá podle toho, jak se posouvám na konkrétní cestě
			foreach (TreePathItem item in items)
			{
				switch (status)
				{
					case "root":
						// Řeším node v root úrovni:
						// Podle klíče položky určím, co v ní je, a jakého typu bude příští položka:
						switch (item.Key)
						{
							case NorisThreadConstants.NodeThread:
								result.AddItem(TraceReader.CreateTreePathItemThread());
								status = "user";               // V příští úrovni cesty (další TreePathItem item in items) očekávám konkrétního uživatele + profil
								break;
						}
						break;
                    case "user":
						// V této úrovni bude login uživatele + profil:
                        result.AddItem(TraceReader.CreateTreePathItemCurrentUser(args, item));
						status = "detail";                     // V příští úrovni cesty budou detailní položky jednotlivého uživatele
						break;
					default:
						result.AddItem(TraceReader.CreateTreePathItemDefault(item.Key));
						break;
				}
			}
			args.TreePath = result;
		}
		/// <summary>
		/// Vrátí definici pro položku grafu s textem "Klienti", která se zobrazuje v root úrovni grafu.
		/// </summary>
		/// <returns></returns>
		internal static TreePathItem CreateTreePathItemThread()
		{
			return new TreePathItem(NorisThreadConstants.NodeThread, "Threads",
				GraphRowDeclarationCls.StandardMultiTask(14, 0.1M, RowProperties.DynamicSubNodeLoading | RowProperties.ChildSortedText));
		}
		/// <summary>
		/// Vrátí definici pro položku grafu s textem "Jeden Klient", která se zobrazuje jako podřízená položka k root nodu Klienti.
		/// </summary>
		/// <returns></returns>
        private static TreePathItem CreateTreePathItemCurrentUser(TraceReaderCreatePathArgs args, TreePathItem item)
		{
            string oneDir = item.Key;
			return new TreePathItem(oneDir, oneDir,
				GraphRowDeclarationCls.StandardMultiTask(14, 0.1M, RowProperties.DynamicElementsLoading));
		}
		/// <summary>
		/// Vrátí definici pro položku grafu defaultní, bez podrobnější znalost věci
		/// </summary>
		/// <returns></returns>
		internal static TreePathItem CreateTreePathItemDefault(string key)
		{
			string name = key;
			return new TreePathItem(key, name,
				GraphRowDeclarationCls.StandardMultiTask(14, 0.1M, RowProperties.PreLoadingElements | RowProperties.DynamicElementsLoading | RowProperties.DynamicSubNodeLoading));
		}
		#endregion
		#region Vytvoření logické události IEventItem na základě dat z trace IEventEdge
		/// <summary>
		/// Tato metoda má za úkol vytvořit a vrátit logickou událost na základě fyzických dat načtených z trace
		/// (před malinkou chvilkou je objekt osobně načetl a vrátil v metodě ReadItem()).
		/// Pokud se vrátí null, pak tato položka trace nemá být zakládána do evidence.
		/// </summary>
		/// <param name="data"></param>
		/// <returns></returns>
		private IEventItem _CreateIEventItem(IEventEdge data)
		{
			if (data == null)
				return null;

			TraceItem item = data as TraceItem;
			if (item == null)
				Msg.Error("Chyba: pro vytvoření objektu IEventItem byl do readeru NorisASrvTraceReader předán nesprávný objekt.\r\nOčekávaný typ: {0}, chybný typ: {1}", typeof(TraceItem).Name, data.GetType().Name);

			EventItem thing = EventItem.CreateEventItem(this.StringHeap, item);

			return thing;
		}
		#endregion
	}
	/// <summary>
	/// Instance této třídy persistuje v ServerInfo.Tag mezi jednotlivými soubory Trace
	/// (tedy mezi jednotlivými voláními metody this._ProcessContent).
	/// Tím je umožněno předávat si kontinuální data do načítání dalšího souboru.
	/// </summary>
	internal class TraceServerPersistData
	{
		internal static TraceServerPersistData GetFrom(object tag)
		{
			TraceServerPersistData data = null;
			if (tag != null && tag is TraceServerPersistData)
				data = tag as TraceServerPersistData;
			if (data == null)
				data = new TraceServerPersistData();
			return data;
		}
		/// <summary>
		/// Datum souboru
		/// </summary>
		internal DateTime TraceFileDate { get; set; }
	}
	#endregion
	#region TraceItem : fyzická data jedné položky (opis položky a ukazatel jejího cíle), načtené ze souboru Aplikační trace Noris. Zde se určují cílové Node pro událost.
	/// <summary>
	/// TraceItem : fyzická data jedné položky, načtené ze souboru Aplikační trace Noris
	/// </summary>
	public class TraceItem : IEventEdge
	{
		#region Konstrukce, parsování
		private TraceItem(StringHeap stringHeap)
		{
			this.StringHeap = stringHeap;
            this.FilterKeyword = "*";
		}
		private StringHeap StringHeap;
		/// <summary>
		/// Vytvoří a vrátí data.
		/// Pokud data nejsou korektně načtena (nesprávný řádek), vrátí null.
		/// </summary>
		/// <param name="stringHeap"></param>
		/// <param name="lineNumber"></param>
		/// <param name="lineText"></param>
		/// <param name="persistData"></param>
		/// <returns></returns>
		internal static TraceItem CreateItem(StringHeap stringHeap, int lineNumber, string lineText, ref TraceServerPersistData persistData)
		{
			TraceItem item = new TraceItem(stringHeap);
			item.LineNumber = lineNumber;
			item.ParseLine(lineText, persistData.TraceFileDate.Date);
			if (!item._LineIsParsed) return null;                               // odbočka při nekorektním tvaru řádku
			//item.DetectTargets();
			return item;
		}
		/// <summary>
		/// Parsování textu řádku do proměnných.
		/// </summary>
		private void ParseLine(string lineText, DateTime traceDate)
		{
			if (String.IsNullOrEmpty(lineText)) return;

            // Struktura řádku:
			//	0		1			2			3		4			5			6			7				8			9				10				11		12			13		14...
			//	Time	ThreadId	ManagedId	User	Profile		Created		Last resume	Last suspend	Suspended	Overall time	Suspend Count	 Type	Action		
			//	00:14:01	1083	-2			POR01JI	Brano A.S.	00:14:00	00:14:00	00:14:00		false		606				0				Both	ItemChanged	

			string[] items = lineText.Split(';');
			int length = items.Length;
			if (length < 14)
				return;

			// Parsování:
			this._Time = MainInfo.ConvertDateTime(traceDate, items[0]);
			Int32.TryParse(items[1], out this._ThreadId);
			Int32.TryParse(items[2], out this._ManagedId);
			this._User = this.StringHeap.GetInt32(items[3]);
			this._Profile = this.StringHeap.GetInt32(items[4]);
			this._Created = MainInfo.ConvertDateTime(traceDate, items[5]);
			this._CreatedSec = this._Created.Second + 60 * this._Created.Minute + 3600 * this._Created.Hour;   // Čas Created v sekundách
			this._LastResume = MainInfo.ConvertDateTime(traceDate, items[6]);
			this._LastSuspend = MainInfo.ConvertDateTime(traceDate, items[7]);
			this._Suspended = (items[9].ToLower() != "false");
			Int32.TryParse(items[9], out this._OverallTime);
			Int32.TryParse(items[10], out this._SuspendCount);
			this._Type = this.StringHeap.GetInt32(items[11]);
			this._Action = this.StringHeap.GetInt32(items[12]);

			// Descriptions:
			if (length >= 14)
			{
				int descriptionsLength = length - 13;   // Počet položek UserNnn je = počet položek nad rámec základních 13

				this._DescriptionsLength = descriptionsLength;
				this._Descriptions = new Int32[descriptionsLength];
				for (int i = 0; i < descriptionsLength; i++)
					this._Descriptions[i] = this.StringHeap.GetInt32(items[13 + i]);
			}
			else
			{
				this._DescriptionsLength = 0;
				this._Descriptions = null;
			}

			// Dokončení:
			this._LineIsParsed = true;
		}
		// Proměnné typované Int32 obsahují pointer na string do StringHeap:
		private DateTime _Time;
		private int _ThreadId;
		private int _ManagedId;
		private Int32 _User;
		private Int32 _Profile;
		private DateTime _Created;
		private int _CreatedSec;
		private DateTime _LastResume;
		private DateTime _LastSuspend;
		private bool _Suspended;
		private int _OverallTime;
		private int _SuspendCount;
		private Int32 _Type;
		private Int32 _Action;
		private int _DescriptionsLength;
		private Int32[] _Descriptions;

		private bool _LineIsParsed;
		#endregion
		#region IEventPointer, IDisposable Members
		/// <summary>
		/// Klíč procesu. Nemůže být prázdný (pak se event nikam nezařadí; ledaže bychom povolili jeden empty proces - ale zatím ne).
		/// </summary>
		string IEventPointer.ProcessKey { get { return this.ProcessKey; } }
		/// <summary>
		/// Klíč události. Může být empty, pak ale nebude možno do této události přidávat další data o události (IEventEdge), 
		/// protože je nebude možno spárovat.
		/// </summary>
        string IEventPointer.EventKey { get { return this.EventKey; } }
		/// <summary>
		/// Pořadové číslo události v rámci trace souboru, 
		/// slouží k přesnému třídění pořadí událostí tehdy, když čas dvou sousedních událostí je zcela shodný.
		/// Ono za 1 milisekundu lze stihnout desítky věcí...
		/// </summary>
		int IEventPointer.LineNumber { get { return this.LineNumber; } }
		void IDisposable.Dispose()
		{
			this._Descriptions = null;

			this.FilterKeyword = null;
		}
		#endregion
		#region Property řádku - načtené z fyzického podkladu
		public int LineNumber { get; private set; }
		public DateTime Time { get { return _Time; } }
		public int ThreadId { get { return _ThreadId; } }
		public int ManagedId { get { return _ManagedId; } }
		public string User { get { return this.StringHeap.GetString(_User); } }
		internal Int32 UserInt32 { get { return _User; } }
		public string Profile { get { return this.StringHeap.GetString(_Profile); } }
		internal Int32 ProfileInt32 { get { return _Profile; } }
		public DateTime Created { get { return _Created; } }
		public int CreatedSec { get { return _CreatedSec; } }
		public DateTime LastResume { get { return _LastResume; } }
		public DateTime LastSuspend { get { return _LastSuspend; } }
		public bool Suspended { get { return _Suspended; } }
		public int OverallTime { get { return _OverallTime; } }
		public int SuspendCount { get { return _SuspendCount; } }
		public string Type { get { return this.StringHeap.GetString(_Type); } }
		internal Int32 TypeInt32 { get { return _Type; } }
		public string Action { get { return this.StringHeap.GetString(_Action); } }
		internal Int32 ActionInt32 { get { return _Action; } }
		public int DescriptionsLength { get { return _DescriptionsLength; } }
		public Int32[] DescriptionsInt32 { get { return _Descriptions; } }
		/// <summary>
		/// Klíč procesu = User.Profile
		/// </summary>
		public string ProcessKey { get { return this.StringHeap.GetString(_User) + "." + this.StringHeap.GetString(_Profile); } }
        /// <summary>
        /// Klíč události
        /// </summary>
        public string EventKey { get { return this._ThreadId.ToString() + "." + this._CreatedSec.ToString(); } }

		#endregion
		#region Podpora pro filtrování
		/// <summary>
		/// Klíčové slovo pro filtraci událostí.
		/// Uživatel může ve vlastnostech projektu určit, jaké události chce načítat (specifikuje je jako soupis stringových konstant).
		/// Každá událost pak musí uvádět svoje klíčové slovo, které je při načítání dat porovnáváno se zadaným filtrem, a pouze vyhovující události se přebírají do systému.
		/// Reader události nefiltruje, to zajišťuje datový systém.
		/// Názvy položek filtru jsou uvedeny na třídě TraceDocument jako konstanty začínající slovem FILTER_*.
		/// Speciální význam mají hodnoty: 
		/// a) hodnota null nebo empty: událost bude zobrazena jen v případě, kdy uživatel zaškrtne VŠE
		/// b) hodnota * bude zobrazena vždy, i když by uživatel nic nepovolil
		/// </summary>
		public string FilterKeyword { get; set; }
		/// <summary>
		/// Určí hodnotu FilterKeyword
		/// </summary>
		protected void DetectFilter()
		{
            // Z klienta budu zobrazovat všechno:
            this.FilterKeyword = "*";
		}
		#endregion
	}
	#endregion
	#region Konstanty NorisClient
	/// <summary>
	/// Konstanty používané v datech klientského trace
	/// </summary>
	public class NorisThreadConstants
	{
		/// <summary>
		/// NorisAplServerThreads (druh dat v trace souboru)
		/// </summary>
        public const string TraceType = "NorisAplServerThreads";
		/// <summary>
        /// 10_Threads
		/// </summary>
		internal const string NodeThread = "10_Threads";
		/// <summary>
        /// \10_Threads
		/// </summary>
		internal const string RootNodeClient = "\\" + NodeThread;
        /// <summary>
        /// GreenThreads = název logického serveru v TraceData
        /// </summary>
        internal const string ServerName = "GreenThreads";
	}
	#endregion
}
